package com.breeze.components.redis.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson2.JSON;
import com.breeze.components.core.exception.ApiException;
import com.breeze.components.core.exception.error.ErrorCode;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

/**
 * spring redis 工具类
 *
 * @author breeze
 */
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Slf4j
@Component
@RequiredArgsConstructor
public class RedisUtil {

  public final RedisTemplate redisTemplate;

  /**
   * 缓存基本的对象，Integer、String、实体类等
   *
   * @param key 缓存的键值
   * @param value 缓存的值
   */
  public <T> void setCacheObject(final String key, final T value) {
    redisTemplate.opsForValue().set(key, value);
  }

  /**
   * 缓存基本的对象，Integer、String、实体类等
   *
   * @param key 缓存的键值
   * @param value 缓存的值
   * @param timeout 时间
   * @param timeUnit 时间颗粒度
   */
  public <T> void setCacheObject(
      final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
    redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
  }

  public void increment(final String key, int value, int expire, TimeUnit timeUnit) {
    redisTemplate.opsForValue().increment(key, value);
    redisTemplate.expire(key, expire, timeUnit);
  }

  /**
   * 设置有效时间
   *
   * @param key Redis键
   * @param timeout 超时时间
   * @return true=设置成功；false=设置失败
   */
  public boolean expire(final String key, final long timeout) {
    return expire(key, timeout, TimeUnit.SECONDS);
  }

  /**
   * 设置有效时间
   *
   * @param key Redis键
   * @param timeout 超时时间
   * @param unit 时间单位
   * @return true=设置成功；false=设置失败
   */
  public Boolean expire(final String key, final long timeout, final TimeUnit unit) {
    return redisTemplate.expire(key, timeout, unit);
  }

  /**
   * 获得缓存的基本对象。
   *
   * @param key 缓存键值
   * @return 缓存键值对应的数据
   */
  public <T> T getCacheObject(final String key) {
    ValueOperations<String, T> operation = redisTemplate.opsForValue();
    return operation.get(key);
  }

  /** 删除单个对象 */
  public void deleteObject(final String key) {
    redisTemplate.delete(key);
  }

  /**
   * 删除集合对象
   *
   * @param collection 多个对象
   */
  public Long deleteObject(final Collection collection) {
    return redisTemplate.delete(collection);
  }

  /**
   * 缓存List数据
   *
   * @param key 缓存的键值
   * @param dataList 待缓存的List数据
   * @return 缓存的对象
   */
  public <T> long setCacheList(final String key, final List<T> dataList) {
    Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
    return count == null ? 0 : count;
  }

  /**
   * 缓存List数据
   *
   * @param key 缓存的键值
   * @param dataList 待缓存的List数据
   * @return 缓存的对象
   */
  public <T> long setCacheList(
      final String key, final List<T> dataList, final long timeout, final TimeUnit timeUnit) {
    Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
    redisTemplate.expire(key, timeout, timeUnit);
    return count == null ? 0 : count;
  }

  /**
   * 获得缓存的list对象
   *
   * @param key 缓存的键值
   * @return 缓存键值对应的数据
   */
  public <T> List<T> getCacheList(final String key, int start, int end) {
    return redisTemplate.opsForList().range(key, start, end);
  }

  /** trim list */
  public void trimCacheList(final String key, final int start, final int end) {
    redisTemplate.opsForList().trim(key, start, end);
  }

  /**
   * 缓存Set
   *
   * @param key 缓存键值
   * @param dataSet 缓存的数据
   * @return 缓存数据的对象
   */
  public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
    BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
    for (T t : dataSet) {
      setOperation.add(t);
    }
    return setOperation;
  }

  /** 获得缓存的set */
  public <T> Set<T> getCacheSet(final String key) {
    return redisTemplate.opsForSet().members(key);
  }

  /** 缓存Map */
  public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
    if (dataMap != null) {
      redisTemplate.opsForHash().putAll(key, dataMap);
    }
  }

  /** 获得缓存的Map */
  public <T> Map<String, T> getCacheMap(final String key) {
    return redisTemplate.opsForHash().entries(key);
  }

  /**
   * 往Hash中存入数据
   *
   * @param key Redis键
   * @param hKey Hash键
   * @param value 值
   */
  public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
    redisTemplate.opsForHash().put(key, hKey, value);
  }

  /**
   * 获取Hash中的数据
   *
   * @param key Redis键
   * @param hKey Hash键
   * @return Hash中的对象
   */
  public <T> T getCacheMapValue(final String key, final String hKey) {
    HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
    return opsForHash.get(key, hKey);
  }

  /** 删除Hash中的数据 */
  public void delCacheMapValue(final String key, final String hKey) {
    HashOperations hashOperations = redisTemplate.opsForHash();
    hashOperations.delete(key, hKey);
  }

  /** 删除Hash中的数据 */
  public void delCacheMapValues(final String key, final Collection<String> hKey) {
    HashOperations hashOperations = redisTemplate.opsForHash();
    hashOperations.delete(key, hKey.toArray());
  }

  /**
   * 获取多个Hash中的数据
   *
   * @param key Redis键
   * @param hKeys Hash键集合
   * @return Hash对象集合
   */
  public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
    return redisTemplate.opsForHash().multiGet(key, hKeys);
  }

  /**
   * 获得缓存的基本对象列表
   *
   * @param pattern 字符串前缀
   * @return 对象列表
   */
  public Collection<String> keys(final String pattern) {
    return redisTemplate.keys(pattern);
  }

  /**
   * List入队
   *
   * @param key 缓存的键值
   * @param data data
   * @return 操作行数
   */
  public <T> long listLeftPush(final String key, final T data) {
    Long count = redisTemplate.opsForList().leftPush(key, data);
    return count == null ? 0 : count;
  }

  /**
   * List入队
   *
   * @param key 缓存的键值
   * @param data data
   * @return 操作行数
   */
  public <T> long listLeftPushAll(final String key, final List<T> data) {
    Long count = redisTemplate.opsForList().leftPushAll(key, data);
    return count == null ? 0 : count;
  }

  /**
   * List right pop
   *
   * @param key 缓存的键值
   * @return 值
   */
  public List<Object> listRightPop(final String key, final int count) {
    return redisTemplate.opsForList().rightPop(key, count);
  }

  /**
   * List删除
   *
   * @param key 缓存的键值
   * @param data 删除的data
   * @return 操作行数
   */
  public <T> long listLeftRemove(final String key, final T data) {
    Long count = redisTemplate.opsForList().remove(key, 0, data);
    return count == null ? 0 : count;
  }

  /**
   * 删除key
   *
   * @param key 缓存的键值
   * @return 是否成功
   */
  public boolean removeKey(final String key) {
    return Boolean.TRUE.equals(redisTemplate.delete(key));
  }

  /**
   * hash incr
   *
   * @param cacheKey 缓存的键值
   * @param hashKey hash key值
   * @param delta incr的值
   * @return 值
   */
  public long hashIncr(final String cacheKey, final String hashKey, final long delta) {
    return redisTemplate.opsForHash().increment(cacheKey, hashKey, delta);
  }

  public Boolean hasKey(String key) {
    return redisTemplate.hasKey(key);
  }

  public void repeatCheck(String key, String exist, long expire, TimeUnit timeUnit) {
    Boolean isExist = this.hasKey(key);
    Assert.isFalse(isExist, () -> new ApiException(ErrorCode.Client.REPEAT_REQUEST));
    redisTemplate.opsForValue().set(key, exist, expire, timeUnit);
  }

  public Set<String> getDifference(String key, String key1) {
    return redisTemplate.opsForSet().difference(key, key1);
  }

  public void removeRangeByScore(String key, int min, Integer max) {
    redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
  }

  public <T> List<T> reverseRangeWithScores(String key, int min, int max, Class<T> clazz) {
    Set<ZSetOperations.TypedTuple<String>> range =
        redisTemplate.opsForZSet().reverseRangeWithScores(key, min, max);
    if (CollUtil.isEmpty(range)) {
      return ListUtil.empty();
    }
    return range.stream()
        .filter(Objects::nonNull)
        .map(tuple -> JSON.parseObject(tuple.getValue(), clazz))
        .collect(Collectors.toList());
  }

  public void addZSet(String key, Set<ZSetOperations.TypedTuple<String>> members) {
    if (CollUtil.isEmpty(members)) {
      return;
    }
    redisTemplate.opsForZSet().add(key, members);
  }

  public void addZSet(String key, String member, double score) {
    if (Objects.isNull(member)) {
      return;
    }
    redisTemplate.opsForZSet().add(key, member, score);
  }
}
